const fs = require("fs");
const crypto = require('crypto');
const DateTime = require("./datetime");
const random = require("./random");
const httpclient = require("./http_client");
const WXBizDataCrypt = require('./WXBizDataCrypt');
const md5 = require('md5');
const xml2js = require('xml2js');
//xml->json
const xmlParser = new xml2js.Parser({explicitArray : false, ignoreAttrs : true})
//json->xml
const jsonBuilder = new xml2js.Builder();


class IWeixin {
    static currencySign(obj, key) {
        let params = Object.keys(obj).map(function (key) {
            return `${key}=${obj[key]}`;
        }).join("&");
        let tmp = `${params}&key=${key}`;
        // global.log.print(tmp);
        return md5(tmp).toUpperCase();
    }
    static createNonceStr(length = 16) {
        const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        let str = "";
        for (let i = 0; i < length; i++) {
            str += chars.charAt(random.randomInt(chars.length));
        }
        return str;
    }
    static async parseXml(xmlstr) {
        return new Promise(function (resolve, reject) {
            xmlParser.parseString(xmlstr, function (err, res) {
                if (err) {
                    reject(err);
                } else if (res.xml) {
                    resolve(res.xml);
                } else {
                    resolve(null);
                }
            });
        })
    }
    printError(error) {
        if (global.log) {
            global.log.error(error);
        } else {
            console.error(error);
        }
    }
    //通过appID获取微信服务端AccessToken，获取一次可以用很长时间
    async accessToken(filePath, appID, appSecret) {
        let record = null;
        let path = filePath+"access_token.txt";
        try {
            let file = fs.readFileSync(path);
            record = JSON.parse(file);
        } catch (error) {
            this.printError(error);
        }
        if (record==null || record.expire_time<DateTime.now()) {
            let url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appSecret}`;
            let token = "";
            try {
                let data = await httpclient.httpsGet(url);
                // console.log(data);
                let obj = JSON.parse(data.body.toString());
                token = obj.access_token;
                obj.expire_time = DateTime.now()+obj.expires_in-120;
                fs.writeFileSync(path, JSON.stringify(obj));
            } catch (error) {
                this.printError(error);
            }
            return token;
        } else {
            return record.access_token;
        }
    }
}

class WeixinGZH extends IWeixin {
    constructor(cfg){
        super();
        this.cfg = cfg;
    }
    //通过appID获取微信服务端AccessToken，获取一次可以用很长时间
    async getAccessToken() {
        return await this.accessToken(this.cfg.filePath, this.cfg.appID, this.cfg.appSecret);
    }
    //通过微信web的服务端AccessToken获取JsApiTicket
    async getJsApiTicket() {
        let record = null;
        let path = this.cfg.filePath+"js_ticket.txt";
        try {
            let file = fs.readFileSync(path);
            record = JSON.parse(file);
        } catch (error) {
            this.printError(error);
        }
        if (record==null || record.expire_time<DateTime.now()) {
            let ticket = "";
            try {
                let token = await this.getAccessToken();
                let url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token="+token;
                let data = await httpclient.httpsGet(url);
                // console.log(data);
                let obj = JSON.parse(data.body.toString());
                ticket = obj.ticket;
                obj.expire_time = DateTime.now()+obj.expires_in-120;
                fs.writeFileSync(path, JSON.stringify(obj));
            } catch (error) {
                this.printError(error);
            }
            return ticket;
        } else {
            return record.ticket;
        }
    }
    //计算微信web接口的签名
    async getSignPackage(url) {
        let timestamp = DateTime.now();
        try {
            let jsapiTicket = await this.getJsApiTicket();
            let nonceStr = IWeixin.createNonceStr();
            let str = `jsapi_ticket=${jsapiTicket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`;
            let sig = crypto.createHash('sha1').update(str).digest("hex");
            // console.log(sig);
            return {
                appId: this.cfg.appID,
                nonceStr: nonceStr,
                timestamp: timestamp,
                url: url,
                signature: sig,
                rawString: str
            };
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //微信web前端的AccessToken
    async getCodeToken(code) {
        try {
            let date = await httpclient.httpsGet(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${this.cfg.appID}&secret=${this.cfg.appSecret}&code=${code}&grant_type=authorization_code`);
            return JSON.parse(date.body.toString());
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //公众号通知
    async gzhSendNotice(openid, templateId, template, url) {
        try {
            const token = await this.getAccessToken();
            const ret = await httpclient.httpsPost(`https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`, JSON.stringify({
                touser: openid,
                template_id: templateId,
                url: url,
                data: template
            }));
            return ret.body.toString();
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //服务端获取微信用户信息
    async getUserinfo(token, openid) {
        try {
            const date = await httpclient.httpsGet(`https://api.weixin.qq.com/sns/userinfo?access_token=${token}&openid=${openid}&lang=zh_CN`);
            return JSON.parse(date.body.toString());
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
}

class WeixinXCX extends IWeixin {
    constructor(cfg){
        super();
        this.cfg = cfg;
    }
    //通过appID获取微信服务端AccessToken，获取一次可以用很长时间
    async getAccessToken() {
        return await this.accessToken(this.cfg.filePath, this.cfg.appID, this.cfg.appSecret);
    }
    //微信小程序前端的Session
    async code2Session(code) {
        try {
            let date = await httpclient.httpsGet(`https://api.weixin.qq.com/sns/jscode2session?appid=${this.cfg.appID}&secret=${this.cfg.appSecret}&js_code=${code}&grant_type=authorization_code`);
            return JSON.parse(date.body.toString());
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //服务端获取微信用户信息
    async getUserinfo(token, openid) {
        try {
            const date = await httpclient.httpsGet(`https://api.weixin.qq.com/sns/userinfo?access_token=${token}&openid=${openid}&lang=zh_CN`);
            return JSON.parse(date.body.toString());
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //小程序用户信息解密
    decodeUserInfo(sessionKey, iv, encryptedData) {
        const pc = new WXBizDataCrypt(this.cfg.appID, sessionKey);
        return pc.decryptData(encryptedData , iv);
    }
    //公众号通知
    async gzhSendNotice(gzhAppID, openid, templateId, template, miniurl) {
        try {
            const token = await this.getAccessToken();
            let param = {
                touser: openid,
                mp_template_msg: {
                    appid: gzhAppID,
                    template_id: templateId,
                    data: template
                }
            }
            if (miniurl) {
                param.mp_template_msg.miniprogram = {
                    appid: this.cfg.appID,
                    pagepath: miniurl
                }
            }
            const ret = await httpclient.httpsPost(`https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=${token}`, JSON.stringify(param));
            return ret.body.toString();
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
    //生成小程序二维码
    async getXcxQR(param, page) {
        try {
            const token = await this.getAccessToken();
            const data = await httpclient.httpsPost(`https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${token}`, JSON.stringify({
                scene: param,
                page: page
            }));
            const ret = {errcode:0, buffer:null};
            try {
                const r = JSON.parse(data.body.toString());
                ret.errcode = r.errcode;
                this.printError(r.errmsg);
            } catch (error) {
                ret.buffer = data.body;
            }
            return ret;
        } catch (error) {
            this.printError(error);
            return null;
        }
    }
}

class WeixinPay extends IWeixin {
    constructor(cfg){
        super();
        this.cfg = cfg;
        if (cfg.p12 && cfg.p12.length) {
            this.pfx = {
                file: fs.readFileSync(cfg.p12),
                pwd: cfg.mchID
            }
        }
    }
    //统一支付
    async unifiedorderPay(name, price, order, openid, attach) {
        const now = new DateTime();
        let param = {
            appid: this.cfg.appID,
            attach: attach,
            body: name,
            mch_id: this.cfg.mchID,
            nonce_str: IWeixin.createNonceStr(),
            notify_url: this.cfg.payCallback,
            openid: openid,
            out_trade_no: order,
            spbill_create_ip: this.cfg.serverIP,
            time_expire: this.cfg.expire?(now.addMinute(this.cfg.expire).datetimeNumber()):(now.addMinute(60).datetimeNumber()),
            total_fee: price,
            trade_type: "JSAPI"
        }
        let sign = IWeixin.currencySign(param, this.cfg.mchKey);
        param.sign = sign;
        let jsonxml = jsonBuilder.buildObject(param);
        try {
            let date = await httpclient.httpsPost("https://api.mch.weixin.qq.com/pay/unifiedorder", jsonxml, "xml");
            let xml = await IWeixin.parseXml(date.body.toString());
            if (xml && xml.return_code=="SUCCESS" && xml.result_code=="SUCCESS") {
                let param2 = {
                    appId:this.cfg.appID,
                    nonceStr:param.nonce_str,
                    package:`prepay_id=${xml.prepay_id}`,
                    signType:"MD5",
                    timeStamp:(now.timestamp()).toString()
                }
                param2.paySign = IWeixin.currencySign(param2, this.cfg.mchKey);
                delete param2.appId;
                return param2;
            } else {
                this.printError(`统一支付请求失败：${xml.return_msg}|${xml.err_code_des?xml.err_code_des:""}`);
                this.printError(JSON.stringify(param));
                return null;
            }
        } catch (error) {
            this.printError(error);
            this.printError(JSON.stringify(param));
            return null;
        }
    }
    //申请退款
    async payRefund(totalPrice, refundPrice, order, transactionId) {
        let param = {
            appid: this.cfg.appID,
            mch_id: this.cfg.mchID,
            nonce_str: IWeixin.createNonceStr(),
            out_refund_no: order,
            refund_fee: refundPrice,
            spbill_create_ip: this.cfg.serverIP,
            total_fee: totalPrice,
            transaction_id: transactionId
        }
        let sign = IWeixin.currencySign(param, this.cfg.mchKey);
        param.sign = sign;
        let jsonxml = jsonBuilder.buildObject(param);
        try {
            let date = await httpclient.httpsPost("https://api.mch.weixin.qq.com/secapi/pay/refund", jsonxml, "xml", null, this.pfx);
            let xml = await IWeixin.parseXml(date.body.toString());
            if (xml && xml.return_code=="SUCCESS" && xml.result_code=="SUCCESS") {
                return xml.refund_id;
            } else {
                this.printError("退款请求失败："+date);
                this.printError(JSON.stringify(param));
                return null;
            }
        } catch (error) {
            this.printError(error);
            this.printError(JSON.stringify(param));
            return null;
        }
    }
}

module.exports.WeixinGZH = WeixinGZH;
module.exports.WeixinXCX = WeixinXCX;
module.exports.WeixinPay = WeixinPay;

// let cfg = {
//     gzh:{
//         filePath:"./save/xx_gzh_",
//         appID:"",
//         appSecret:""
//     },
//     xcx:{
//         filePath:"./save/xx_xcx_",
//         appID:"",
//         appSecret:"",
//     },
//     shanghu:{
//         serverIP:"127.0.0.1",
//         appID:"",
//         expire:10,//订单过期时间（分钟）,默认60分钟
//         mchID:"",
//         mchKey:"",
//         payCallback:"https://127.0.0.1:8102/pay_callback.html"
//     },
// }